package com.franklin.java.completion.map.mybatis;

import com.flychord.jdk.util.tuple.Tuple2;
import com.franklin.java.api.annotation.BaseAnnotation;
import com.franklin.java.entity.sql.ColumnInfo;
import com.franklin.java.service.database.DataSourceServiceSupport;
import com.franklin.java.service.database.DatabaseHandler;
import com.franklin.java.utils.EntityClassHelper;
import com.franklin.plugins.common.collection.PrettyArrayList;
import com.franklin.plugins.common.collection.PrettyHashSet;
import com.franklin.java.api.mybatis.mybatisplus.annotation.MPAnnotations;
import com.franklin.java.completion.map.MapData;
import com.franklin.java.utils.PsiUtils;
import com.franklin.plugins.common.utils.DataCheckUtils;
import com.franklin.plugins.common.utils.PatternUtils;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import static com.flychord.jdk.util.tuple.TupleAble.tuple;

/**
 * Mybatis-Plus方法处理器
 *
 * @author Franklin
 * @since 2021/8/26 12:03
 */
public class MybatisPlusMethodHandler extends MybatisMethodHandler<PsiMethodCallExpression> {

    private static Set<String> mpMethodSet = new PrettyHashSet<>();

    private static final String LAMBDA_WRAPPER_PATTERN = "^LambdaQueryWrapper<\\w*>(?<lambdaWrapper>.*)=%s[\\S\\s]*$";

    static {
        mpMethodSet.add("getMap");
        mpMethodSet.add("listMaps");
        mpMethodSet.add("pageMaps");
    }

    public MybatisPlusMethodHandler(Project myProject) {
        super(myProject);
    }

    @Override
    public List<MapData> resolveMethod(PsiMethodCallExpression psiElement) {
        return resolveMpMethod(psiElement);
    }

    @Override
    public List<ColumnInfo> resolveColumnInfo(PsiMethodCallExpression psiElement) {
        PsiMethodCallExpression psiMethodCallExpression = Optional.ofNullable(psiElement)
            .map(element -> (PsiMethodCallExpression)element)
            .filter(element -> {
                PsiMethod psiMethod = element.resolveMethod();
                if (Objects.nonNull(psiMethod)) {
                    String name = psiMethod.getName();
                    return mpMethodSet.contains(name);
                }
                return false;
            })
            .map(PsiUtils::findFullMethodCallExpression)
            .orElse(null);
        if (Objects.isNull(psiMethodCallExpression)) {
            return null;
        }
        PsiExpressionList argumentList = psiMethodCallExpression.getArgumentList();
        PsiExpression[] expressions = argumentList.getExpressions();
        Tuple2<PsiClass, String> tableName = resolveTableName(psiElement);
        List<ColumnInfo> columnInfos = Collections.emptyList();
        if (ArrayUtils.isEmpty(expressions) || isPageWithoutQueryWrapper(psiElement, expressions)) {
            //空参方法,直接解析实体
            columnInfos = EntityClassHelper.resolveColumnInEntity(tableName._1(), tableName._2());
        }else {
            //解析QueryWrapper里面的Select方法
            columnInfos = resolveColumnInQueryWrapper(psiElement, expressions[expressions.length - 1], tableName._2());
            if (DataCheckUtils.isEmpty(columnInfos)){
                //没有使用Select方法，返回实体所有属性
                columnInfos = EntityClassHelper.resolveColumnInEntity(tableName._1(), tableName._2());
            }
        }
        DataSourceServiceSupport.getInstance().getDatabaseHandler(psiElement).resolveColumnInfo(columnInfos);
        return columnInfos;
    }

    private List<MapData> resolveMpMethod(PsiMethodCallExpression psiElement) {
        List<ColumnInfo> columnInfos = resolveColumnInfo(psiElement);
        if (DataCheckUtils.isNotEmpty(columnInfos)){
            DatabaseHandler databaseHandler = DataSourceServiceSupport.getInstance()
                .getDatabaseHandler(psiElement);
            databaseHandler.resolveColumnInfo(columnInfos);

            return this.sqlHelper.convertColumnToMapData(columnInfos);
        }
        return Collections.emptyList();
    }

    /**
     * 解析表名<br>
     * @author Franklin
     * @since 2021/8/26 18:24
     * @param psiElement :
     * @return java.lang.String
     */
    private Tuple2<PsiClass, String> resolveTableName(PsiElement psiElement){
        PsiType psiType = Optional.ofNullable(PsiTreeUtil.getParentOfType(psiElement, PsiClass.class))
            .map(PsiClass::getExtendsListTypes)
            .map(psiClassTypes -> {
                for (PsiClassType psiClassType : psiClassTypes) {
                    String psiClassTypeName = psiClassType.getName();
                    if (psiClassTypeName.equals("ServiceImpl")) {
                        PsiType[] typeParameters = psiClassType.getParameters();
                        if (ArrayUtils.isNotEmpty(typeParameters)) {
                            if (typeParameters.length >= 2) {
                                return typeParameters[1];
                            } else {
                                return typeParameters[0];
                            }
                        }
                    }
                }
                return null;
            })
            .orElse(null);

        PsiClass tableClass = Optional.ofNullable(psiType)
            .map(type -> {
                String typeText = type.getCanonicalText();
                typeText = typeText.replaceAll("Mapper", "");
                typeText = typeText.replaceAll("Dao", "");
                return PsiUtils.findClass(psiElement.getProject(), psiElement.getContainingFile().getVirtualFile().getPath(), typeText);
            })
            .orElse(null);
        String tableName = EntityClassHelper.resolveTableName(tableClass);
        return tuple(tableClass,tableName);
    }

    /**
     * 只有分页参数的分页方法<br>
     *
     * @param psiElement:
     * @param expressions:
     * @return boolean
     * @author Franklin
     * @since 2021/8/26 15:05
     */
    private boolean isPageWithoutQueryWrapper(PsiElement psiElement, PsiExpression[] expressions) {
        return psiElement.getText().equals("pageMaps") && expressions.length == 1;
    }

    /**
     * 解析QueryWrapper中的Select方法<br>
     *
     * @param psiElement:
     * @param psiExpression:
     * @return java.util.List<com.franklin.java.completion.map.MapData>
     * @author Franklin
     * @since 2021/8/26 15:24
     */
    private List<ColumnInfo> resolveColumnInQueryWrapper(PsiElement psiElement, PsiExpression psiExpression,String tableName) {
        String queryWrapperName = psiExpression.getText();
        List<ColumnInfo> mapDataList = new PrettyArrayList<>();

        AtomicReference<String> lambdaWrapperReference = new AtomicReference<>();
        Optional.ofNullable(PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class))
            .map(PsiMethod::getBody)
            .ifPresent(psiMethodBody -> {
                String[] lines = psiMethodBody.getText().split("\n");

                String regex = String.format(LAMBDA_WRAPPER_PATTERN, queryWrapperName);
                Set<PsiMethodCallExpression> selectMethods = new PrettyHashSet<>();

                int length = 0;
                for (String line : lines) {
                    if (line.trim().startsWith("/")) {
                        length = length + line.length() + 1;
                        continue;
                    }
                    String tempLine = StringUtils.deleteWhitespace(line);
                    if (Objects.isNull(lambdaWrapperReference.get())){
                        String lambdaWrapper = PatternUtils.find(regex, tempLine, "lambdaWrapper")
                            .getOrDefault("lambdaWrapper", null);
                        lambdaWrapperReference.set(lambdaWrapper);
                    }


                    if (tempLine.contains(".select")) {
                        //找到select方法
                        PsiElement selectElement = psiMethodBody.findElementAt(length + line.indexOf("select"));
                        PsiMethodCallExpression selectMethodCallExpression = PsiUtils.findFullMethodCallExpression(selectElement);
                        Optional.ofNullable(selectMethodCallExpression)
                            .filter(psiMethodCallExpression -> !selectMethods.contains(psiMethodCallExpression))
                            .map(PsiElement::getText)
                            .map(StringUtils::deleteWhitespace)
                            .filter(chain ->
                                chain.startsWith(queryWrapperName)
                                || (Objects.nonNull(lambdaWrapperReference.get()) && chain.startsWith(lambdaWrapperReference.get()))
                            )
                            .ifPresent(chain -> {
                                selectMethods.add(selectMethodCallExpression);
                                mapDataList.addAll(resolveSimpleSelectMethod(selectMethodCallExpression,tableName));
                            });
                    }

                    length = length + line.length() + 1;
                }
            });

        return mapDataList;
    }

    /**
     * 解析使用String的select方法<br>
     *
     * @param fullSelectMethodExpression :
     * @return void
     * @author Franklin
     * @since 2021/8/26 15:40
     */
    private List<ColumnInfo> resolveSimpleSelectMethod(PsiMethodCallExpression fullSelectMethodExpression, String tableName) {
        //寻找调用链中的所有Select方法
        String selectMethodExpressionText = fullSelectMethodExpression.getText();
        int index = 0;
        int fromIndex = 0;
        List<String> selectColumns = new PrettyArrayList<>();
        while ((index = selectMethodExpressionText.indexOf("select", fromIndex)) >= 0) {
            PsiElement selectElement = fullSelectMethodExpression.findElementAt(index);
            PsiMethodCallExpression selectMethodExpression = PsiTreeUtil.getParentOfType(selectElement, PsiMethodCallExpression.class);
            if (Objects.nonNull(selectMethodExpression)) {
                fromIndex = selectMethodExpression.getTextLength();

                PsiExpressionList argumentList = selectMethodExpression.getArgumentList();
                PsiExpression[] expressions = argumentList.getExpressions();
                Arrays.stream(expressions)
                    .map(psiExpression -> {
                        String colText = psiExpression.getText();
                        if (psiExpression instanceof PsiLiteralExpression) {
                            return colText;
                        } else if (psiExpression instanceof PsiMethodReferenceExpression) {
                            //方法引用
                            colText = StringUtils.deleteWhitespace(colText);
                            String[] methods = colText.split("::");
                            return this.sqlHelper.resolveColumnInGetMethod(methods[1]);
                        }
                        return "";
                    })
                    .filter(DataCheckUtils::isNotEmpty)
                    .forEach(selectColumns::add);
            }
        }
        return this.sqlHelper.resolveMPSqlSelectSegment(selectColumns,tableName);
    }
}
